﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Text.RegularExpressions;

/* Universität Trier
 * Fachbereich II - Linguistische Datenverarbeitung
 * Bachelor of Arts
 * Computerlinguistik (HF), English Language and Linguistics (NF)
 * 
 * 
 * A Heuristic Approach to Anaphora Resolution
 * 
 * Bachelor-Arbeit
 * 15. November 2013
 * 
 * Name:			Christopher Michels
 * Matrikelnummer:	1007830
 * 
 * Adresse:			Universitätsring 8d
 * 
 * Telefon:			(06 51) 99 24 11 55
 * E-Mail:			s2chmich@uni-trier.de
 * 
 * Erstprüfer: 		Dr. Sven Naumann
 * Zweitprüfer:		Prof. Dr. Reinhard Köhler
 * 
 * Sources:
 * Biber, Douglas et al. (1999). Longman Grammar of Spoken and Wri􀆩en English. Studies in Language and Linguistics. Harlow, UK: Longman.
 * Mitkov, Ruslan (1998). “Robust pronoun resolution with limited knowledge”. In: Proceedings of the 36th Annual Meeting of the Association for Computational Linguistics and 17th International Conference on Computational Linguistics. Vol. 2. ACL '98. Montreal, Canada: Association for Computational Linguistics, 869–875.
 * Mitkov, Ruslan (2000). “Pronoun Resolution: The Practical Alternative”. In: Corpus-based and Computational Approaches to Discourse Anaphora. Ed. by Simon Botley and Anthony Mark McEnery. Studies in Corpus Linguistics. J. Benjamins Pub. Co.
 * Mitkov, Ruslan (2002). Anaphora resolution. Studies in Language and Linguistics. London, UK: Longman.
 * Santorini, Beatrice (1991). Part-of-Speech Tagging Guidelines for the Penn Treebank Project. URL: http://www.cis.uni-muenchen.de/~schmid/tools/TreeTagger/data/Penn-Treebank-Tagset.pdf (visited on 11/06/2013).
 * Schmid, Helmut (1994). “Probabilistic Part-of-Speech Tagging Using Decision Trees”. In: Proceedings of the International Conference on New Methods in Language Processing. Manchester, UK.
 * Schmid, Helmut (1995). “Improvements In Part-of-Speech Tagging With an Application To German”. In: Proceedings of the ACL SIGDAT-Workshop. Dublin, Ireland.
 * Textalyser (2004). Bernhard Huber Internet Engineering Company. URL: http://textalyser.net (visited on 10/23/2013).
 */

namespace KPAR
{
    /* This class provides fields and methods to handle the data
     * relevant for an anaphoric pronoun.
     */
    class Pronoun : Anaphora
    {
        /* Initialises the field PronounIDs with an empty string list.
         * One-line field definition would not initialise the field.
         */
        private static List<string> pronounIDs = new List<string>();

        /* Access options for the field PronounIDs.
         */
        public static List<string> PronounIDs
        {
            get
            {
                return pronounIDs;
            }

            private set
            {
                pronounIDs = value;
            }
        }

        /* One-line field definitions for excluded IDs.
         */
        public static List<string> IDsToExclude_PRON { get; set; }

        //Some NCs with deictic pronouns may still be candidates.
        public static List<string> IDsToExclude_DEIX_PRON { get; set; }
        public static List<string> IDsToExclude_DEIX_CAND { get; set; }

        public static List<string> IDsToExclude_DETS { get; set; }
        public static List<string> IDsToExclude_PLIT { get; set; }
        public static List<string> IDsToExclude_WHAT { get; set; }

        /* Field definitions for evaluation purposes.
         */
        private static List<string> trivialIDs = new List<string>();
        public static List<string> TrivialIDs
        {
            get
            {
                return trivialIDs;
            }

            set
            {
                trivialIDs = value;
            }
        }
        private static List<string> nonCriticalIDs = new List<string>();
        public static List<string> NonCriticalIDs
        {
            get
            {
                return nonCriticalIDs;
            }

            set
            {
                nonCriticalIDs = value;
            }
        }

        /* Uses AccessXML to get a list of Pronoun objects based on
         * the anaphoric links to resolve in the created XML file.
         */
        public static List<Pronoun> GetPronouns()
        {
            List<Pronoun> pronouns = new List<Pronoun>();

            //Get a list of the pronoun tokens (NOT: NC elements).
            IEnumerable<XElement> pronounElems = 
                AccessXML.AccessPronouns(PronounTypes);

            //Assemble data and create a Pronoun object for each pronoun token.
            foreach (XElement pronoun in pronounElems)
                pronouns.Add(new Pronoun(AccessXML.AccessPronounData(pronoun)));

            return pronouns;
        }

        #region PronounFields

        public string[] ID { get; private set; }
        public string Number { get; private set; }
        public string Gender { get; private set; }
        public List<Candidate> Candidates { get; private set; }

        public bool IgnoreDefiniteness { get; private set; }

        public string[] CollocData { get; private set; }
        public int NounIDforIR { get; private set; }
        public int[] PrecClauseLimits { get; private set; }
        public List<string> StringsOfParNCsIE { get; private set; }
        public List<string> StringsOfHeadNCsIE { get; private set; }
        public List<string> VCSelectedNPs { get; private set; }
        public List<string> NCSelectedNPs { get; private set; }
        public List<string> FocusedNPs { get; private set; }

        #endregion

        /* Default/void Constructor
         */
        public Pronoun() { }

        /* Main Constructor who gets relevant pronoun data,
         * assigning values to fields required for determining
         * the indicators for all the antecedent candidates.
         */
        public Pronoun(XElement pronounData) 
        {
            //Unpack the pronoun from pronounData.
            XElement pronoun =
                (from p in pronounData.Descendants("NC")
                 where p.Parent.Name == "pronoun"
                 select p).First();

            //Handle NPs with 2 gender/number values 
            //(due to attributively used possessive pronouns)
            //Save basic information.
            if (pronoun.Attribute("gender").Value.Contains(":"))
            {
                ID = pronoun.SplitID();
                Number = pronoun.Attribute("number").Value.Split(new char[] { ':' })[1];
                Gender = pronoun.Attribute("gender").Value.Split(new char[] { ':' })[1];
            }
            else
            {
                ID = pronoun.SplitID();
                PronounIDs.Add(ID.Last());
                Number = pronoun.Attribute("number").Value;
                Gender = pronoun.Attribute("gender").Value;
            }

            //Unpack the rest of the pronounData.
            IEnumerable<XElement> headings =
                from h in pronounData.Descendants("heading")
                select h;

            IEnumerable<XElement> paragraphs =
                from para in pronounData.Descendants("paragraph")
                select para;

            IEnumerable<XElement> sentences =
                from s in pronounData.Descendants("sentence")
                where s.Parent.Name == "sentences"
                select s;

            IEnumerable<XElement> nouns =
                from n in pronounData.Descendants("NC")
                where n.Parent.Name == "nouns"
                select n;

            //Pass unpacked data on for the purpose of evaluation.
            EvaluateData(pronoun, headings, paragraphs, sentences, nouns);
        }

        /* Gets the XElement of the pronoun, the headings of the 
         * pronoun section, the paragraphs with the relevant 
         * sentences, and the NC elements preceding the pronoun in 
         * those sentences; Evaluates the information in these 
         * elements for the purpose of determining the indicators 
         * of its candidates.
         */
        private void EvaluateData(  XElement pronoun, 
                                    IEnumerable<XElement> headings, 
                                    IEnumerable<XElement> paragraphs, 
                                    IEnumerable<XElement> sentences, 
                                    IEnumerable<XElement> nouns)
        {
            #region DefinitenessData

            //Get all the NC elements in the relevant paragraphs.
            IEnumerable<XElement> parNouns =
                from parNoun in paragraphs.Descendants("NC")
                select parNoun;

            bool definite = false;
            bool indefinite = false;

            //Detect whether there are any definite or indefinite 
            //articles used in those NCs.
            foreach (XElement parNoun in parNouns)
            {
                if (parNoun.Descendants().Count() > 1)
                {
                    foreach (XElement nounElem in parNoun.Descendants())
                    {
                        if (DefArticles.Contains(nounElem.Value.ToLower()))
                        {
                            definite = true;
                            break;
                        }
                        if (IndefArticles.Contains(nounElem.Value.ToLower()))
                        {
                            indefinite = true;
                            break;
                        }
                    }
                }

                if (definite || indefinite)
                {
                    break;
                }
            }

            //If so, use Definiteness as an indicator.
            if (definite || indefinite)
                IgnoreDefiniteness = false;
            else
                IgnoreDefiniteness = true;

            #endregion

            #region VerbPreferenceData

            //Find verbs from the verb preference list and the 
            //NCs preferred by them.
            IEnumerable<string> vcSelectedNPs =
                from VC in sentences.Descendants("VC")
                let nextNC = VC.ElementsAfterSelf().FirstOrDefault()
                //Find the last token of the verb, if possible.
                let lemmasVC =
                    from token in VC.Descendants("token")
                    where token.Attribute("pos").Value.StartsWith("V")
                    select token.Attribute("lemma").Value
                //Get the lemma of that token.
                let lastLemmaVC = lemmasVC.LastOrDefault()
                where nextNC != null 
                        && nextNC.Name == "NC"
                        && lastLemmaVC != null
                        && PreferringVCs.Contains(lastLemmaVC)
                //Select the following NC's partial ID.
                select nextNC.SplitID().Last();

            //Save the results as a string list.
            VCSelectedNPs = vcSelectedNPs.ToList();

            #endregion

            #region NounPhrasePreferenceData

            //Find nouns from the noun phrase preference list and the 
            //NCs preferred by them.
            IEnumerable<string> ncSelectedNPs =
                from VC in sentences.Descendants("VC")
                let nextNC = 
                    VC.ElementsAfterSelf("NC").FirstOrDefault()
                let precNC = 
                    VC.ElementsBeforeSelf("NC").LastOrDefault()
                //Find the last noun token of the preceding NC, if possible.
                let lemmasPrecNC =
                    (precNC != null) ?
                        from token in precNC.Descendants("token")
                        where token.Attribute("pos").Value.StartsWith("N")
                        select token.Attribute("lemma").Value : null
                //Get the lemma of that token.
                let lastLemmaPrecNC =
                    (lemmasPrecNC != null) ?
                        lemmasPrecNC.LastOrDefault() : null
                where nextNC != null 
                        && precNC != null
                        && lastLemmaPrecNC != null 
                        && PreferringNPs.Contains(lastLemmaPrecNC)
                //Select the following NC's partial ID.
                select nextNC.SplitID().Last();

            //Save the results as a string list.
            NCSelectedNPs = ncSelectedNPs.ToList();

            #endregion

            #region LexicalReiterationData
            
            //Get the noun token strings and the string of the head noun lemma
            //for each NC in the relevant paragraphs.
            IEnumerable<string> stringsOfParNCsIE =
                from NC in paragraphs.Descendants("NC")
                //Find the noun token strings.
                let partsOfNC = 
                    from token in NC.Descendants("token")
                    where token.Attribute("pos").Value.StartsWith("N")
                    select token.Value
                //Concatenate them to create a single string.
                let stringOfNC = String.Join(" ", partsOfNC).Trim()
                //Find the last noun token of the current NC, if possible.
                let nounLemmas =
                    from token in NC.Descendants("token")
                    where token.Attribute("pos").Value.StartsWith("N")
                    select token.Attribute("lemma").Value
                //Get the lemma of that token.
                let headNounStr = nounLemmas.LastOrDefault()
                where !stringOfNC.Equals("") && (headNounStr != null)
                //Combine the noun token string and the lemma, seperated
                // by a colon.
                select stringOfNC + ":" + headNounStr;

            //Save the results as a string list.
            StringsOfParNCsIE = stringsOfParNCsIE.ToList();

            #endregion

            #region SectionHeadingData

            //Get the noun token strings and the string of the head noun lemma
            //for each NC in the relevant headings.
            IEnumerable<string> stringsOfHeadNCsIE =
                from NC in headings.Descendants("NC")
                //Find the noun token strings.
                let partsOfNC =
                    from token in NC.Descendants("token")
                    where token.Attribute("pos").Value.StartsWith("N")
                    select token.Value
                //Concatenate them to create a single string.
                let stringOfNC = String.Join(" ", partsOfNC).Trim()
                //Find the last noun token of the current NC, if possible.
                let nounLemmas =
                    from token in NC.Descendants("token")
                    where token.Attribute("pos").Value.StartsWith("N")
                    select token.Attribute("lemma").Value
                //Get the lemma of that token.
                let headNounStr = nounLemmas.LastOrDefault()
                where !stringOfNC.Equals("") && (headNounStr != null)
                //Combine the noun token string and the lemma, seperated
                // by a colon.
                select stringOfNC + ":" + headNounStr;

            //Save the results as a string list.
            StringsOfHeadNCsIE = stringsOfHeadNCsIE.ToList();

            #endregion

            #region CollocationPreferencePronounData

            //Find the pronoun unisolated/in its context.
            XElement p =
                (from n in sentences.Descendants("NC")
                 where n.Attribute("id").Value == pronoun.Attribute("id").Value
                 select n).First();

            //Find the neighbours of the pronoun, if possible.
            XElement precedingElemPron = p.ElementsBeforeSelf().LastOrDefault();
            XElement nextElemPron = p.ElementsAfterSelf().FirstOrDefault();

            //Are these neighbours VCs?
            XElement precedingVC_Pron =
                (precedingElemPron != null && precedingElemPron.Name == "VC") ?
                    precedingElemPron : null;
            XElement nextVC_Pron =
                (nextElemPron != null && nextElemPron.Name == "VC") ?
                    nextElemPron : null;

            //VC lemmata go here as results.
            string collocVC_PrePron = null;
            string collocVC_PostPron = null;

            //Find the results based on preceding/following VCs.
            if (precedingVC_Pron != null)
            {
                //Find the last token of the verb, if possible.
                IEnumerable<XElement> tokensOfVC_PrePron =
                    from token in precedingVC_Pron.Descendants("token")
                    where token.Attribute("pos").Value.StartsWith("V")
                    select token;
                XElement lastTokenOfVC_PrePron =
                    tokensOfVC_PrePron.LastOrDefault();

                //Get the lemma of that token.
                if (lastTokenOfVC_PrePron != null)
                    collocVC_PrePron = lastTokenOfVC_PrePron.Attribute("lemma").Value;
            }
            if (nextVC_Pron != null)
            {
                //Find the last token of the verb, if possible.
                XElement lastTokenOfVC_PostPron =
                    (from token in nextVC_Pron.Descendants("token")
                     where token.Attribute("pos").Value.StartsWith("V")
                     select token).LastOrDefault();

                //Get the lemma of that token or, in case of "be", try to get
                //the following adjective (complex).
                if (lastTokenOfVC_PostPron != null)
                {
                    if (lastTokenOfVC_PostPron.Attribute("lemma").Value.Equals("be"))
                    {
                        XElement followingADJC = lastTokenOfVC_PostPron.Parent.ElementsAfterSelf().FirstOrDefault();
                        if (followingADJC != null
                            && followingADJC.Name == "ADJC")
                        {
                            IEnumerable<string> stringsOfADJC =
                                from token in followingADJC.Descendants("token")
                                select token.Value;

                            collocVC_PostPron = String.Join(" ", stringsOfADJC).Trim();
                        }
                        else
                        {
                            collocVC_PostPron =
                                lastTokenOfVC_PostPron.Attribute("lemma").Value;
                        }
                    }
                    else
                    {
                        collocVC_PostPron =
                            lastTokenOfVC_PostPron.Attribute("lemma").Value;
                    }
                }
            }

            //Save results for this pronoun in CollocData.
            CollocData = new string[] { collocVC_PrePron, collocVC_PostPron };

            #endregion

            #region ImmediateReferenceAndReferentialDistanceData

            //Retrieve the sentence of the current pronoun.
            XElement pCurrSent =
                (from s in sentences
                 let sID = s.SplitID()[2]
                 where sID.Equals(ID[2])
                 select s).First(); 

            //VCs are counted for the purpose of determining the number
            //of clauses.
            int countVC = 0;

            //This list stores the clause limiter elements retrieved
            //in the current sentence.
            XElement PreviousElements = new XElement("PreviousElements");

            //This list stores the elements retrieved in the current sentence
            //and formats them to identify immediate reference structures.
            List<string> ListForIR = new List<string>();

            #region ComputePreviousElementsAndListForIR

            //Look at each node in the current sentences and format it appro-
            //priately for the two lists initialised above. Mainly filters
            //NCs (filters you separately for immediate reference), VCs, 
            //conjunctions and other clause limiters. New PreviousElements are
            //added at the beginning of the list.
            foreach (XNode n in pCurrSent.Nodes())
            {
                if (n is XElement)
                {
                    XElement nAsXElem = (XElement)n;
                    XElement lastToken = nAsXElem.Descendants("token").LastOrDefault();

                    //Important to save the ID of a node in case the node is an NC.
                    string nodeID = nAsXElem.Attribute("id").Value.Split(new char[] { ':' }).Last();

                    bool nIsToken = (lastToken == null);
                    bool isPrevElem = Int32.Parse(nodeID) < Int32.Parse(ID[3]);

                    if (nIsToken)
                    {
                        string nodePOS = nAsXElem.Attribute("pos").Value;

                        if (isPrevElem)
                        {
                            if (ClauseLimiterTypes.Contains(nodePOS))
                            {
                                PreviousElements.AddFirst(nAsXElem);
                            }
                            if (nodePOS.StartsWith("IN") &&
                                    nAsXElem.Attribute("prepositional").Value == "False")
                            {
                                ListForIR.Add("CC");
                                PreviousElements.AddFirst(nAsXElem);
                            }
                            else
                            {
                                ListForIR.Add(nodePOS);
                            }
                        }
                    }
                    else
                    {
                        //Count VCs here.
                        if (nAsXElem.Name == "VC")
                            countVC++;
                        if (isPrevElem)
                        {
                            string POSListString = nAsXElem.Name.ToString();
                            if (ClauseLimiterTypes.Contains(lastToken.Attribute("pos").Value))
                                PreviousElements.AddFirst(nAsXElem);
                            if (nAsXElem.Name == "NC")
                            {
                                if (nAsXElem.Descendants().Count() == 1)
                                {
                                    XElement potentialPron = nAsXElem.Descendants().First();
                                    if (PronounTypes.Contains(potentialPron.Attribute("pos").Value) &&
                                            potentialPron.Attribute("lemma").Value == "you")
                                    {
                                        POSListString = "you";
                                    }
                                    else if (PronounTypes.Contains(potentialPron.Attribute("pos").Value) &&
                                            potentialPron.Attribute("lemma").Value == "it")
                                    {
                                        POSListString = "it";
                                    }
                                    else
                                    {
                                        POSListString += ":" + nodeID;
                                    }
                                }
                                else
                                {
                                    POSListString += ":" + nodeID;
                                }
                            }
                            else if (nAsXElem.Name == "VC")
                            {
                                if (nAsXElem.Descendants("token").First().Attribute("pos").Value == "CC")
                                {
                                    ListForIR.Add("CC");
                                }
                                if (nAsXElem.Descendants("token").First().Attribute("pos").Value == "TO")
                                {
                                    ListForIR.Add("TO");
                                }
                                PreviousElements.AddFirst(nAsXElem);
                            }
                            ListForIR.Add(POSListString);
                        }
                    }
                }
            }

            #endregion

            //Pass the list of elements for immediate reference on to identify 
            //an NC matching immediate reference, and save the result.
            NounIDforIR = CheckImmediateRef(ListForIR);

            //Pass the list of clause limiters on to identify the limits of the
            //clause preceding the clause of the pronoun and save the result.
            PrecClauseLimits = GetPrecClauseLimits(countVC, PreviousElements);

            #endregion

            #region AntecedentPointingConstructionsData

            //Find and store the partial IDs of NCs emphasised by cleft
            //constructions or prompts.
            FocusedNPs = FindFocusedNPs(sentences);

            #endregion

            //Assemble the data relevant for the antecedent candidates.
            XElement allCandidatesData = new XElement("allCandidatesData");
            allCandidatesData.Add(new XElement("sentences", sentences));
            allCandidatesData.Add(new XElement("nouns", nouns));

            //Get the list of Candidate objects. Pass on the list of 
            //pronoun-NC IDs so they are not considered candidates themselves.
            List<string> excludeIDs =
                IDsToExclude_PRON.Concat(
                    IDsToExclude_DEIX_CAND).Concat(
                        IDsToExclude_PLIT).Concat(
                            IDsToExclude_WHAT).ToList();

            //Find IDs of trivial or non-critical pronouns.
            if (nouns.Count() == 1)
                TrivialIDs.Add(ID.Last());

            Candidates = 
                Candidate.GetCandidates(
                    excludeIDs.Concat(PronounIDs).ToList(), 
                    Number, Gender, allCandidatesData);

            if (Candidates.Count() == 1
                && !TrivialIDs.Contains(ID.Last()))
                NonCriticalIDs.Add(ID.Last());
        }

        /* Takes a list of elements formatted for the immediate reference
         * indicator and tries to find a matching NC's ID.
         */
        private int CheckImmediateRef(List<string> ListForIR)
        {
            //Reverse the order of the list of elements for immediate reference.
            ListForIR.Reverse();

            //Use regular expressions to identify clefts and prompts.
            //Store partial IDs of focused NPs via groups -> (...). 
            //Index of groups starts with 1.
            Match immediateRefMatch = Regex.Match(
                String.Join("_" , ListForIR),
                @"^((it_)?(VC){1,1}_(you_)?(,|CC|ADVC|CC_,|ADVC_,|ADVC_CC_,)_)+?(PRT_|PC_)?NC:([0-9]+)_VC(_you)?(?!_TO)", 
                RegexOptions.IgnoreCase);

            if (immediateRefMatch.Success)
            {
                return Int32.Parse(immediateRefMatch.Groups[7].Value);
            }
            else
            {
                return -1;
            }
        }

        /* Takes the number of VCs in the current sentence of the pronoun
         * as well as an XElement containing the XElements preceding the
         * pronoun in reversed order; Tries to find the limits of the clause
         * which precedes the clause containing the pronoun and returns an
         * integer array with the start and the end ID of that clause.
         * If there is only one preceding clause, clause start ID may be -1.
         * If there are not at least two clauses/VCs present in the current
         * sentence, both limits are -1.
         */
        private int[] GetPrecClauseLimits(int countVC, XElement PreviousElements)
        {
            //Get an Enumerator of the elements in PreviousElements.
            IEnumerable<XElement> clauseInfos =
                from info in PreviousElements.Elements()
                select info;

            //Initial values for the clause limits.
            int clauseEnd = -1;
            int clauseStart = -1;

            //There have to be at least 2 clauses in the current sentence and
            //clause information has to be present.
            if (countVC > 1 && clauseInfos.Count() > 0)
            {
                //If the piece of clause information is a VC, one VC is skipped
                //because it either belongs to the clause of the pronoun(skippedVCs_CE) 
                //or to the clause preceding that clause(skippedVCs_CS). Only the next
                //VC or the next non-VC clause information is a valid limit.
                int skippedVCs_CE = 0;
                int skippedVCs_CS = 0;

                foreach (XElement clauseInfo in clauseInfos)
                {
                    //Store the partial ID of the current clause information.
                    int clauseInfoPartID = Int32.Parse(clauseInfo.Attribute("id").Value.Split(new char[] { ':' }).Last());

                    //Find a clause end first. Remember the reversed order!
                    if (clauseEnd == -1)
                    {
                        if (clauseInfo.Name != "VC")
                        {
                            clauseEnd = clauseInfoPartID;
                        }
                        else
                        {
                            if (++skippedVCs_CE == 2)
                            {
                                clauseEnd = clauseInfoPartID;
                            }
                        }
                    }
                    //Try to find a clause start then.
                    else if (clauseStart == -1)
                    {
                        if (clauseInfo.Name != "VC")
                        {
                            clauseStart = clauseInfoPartID;
                        }
                        else
                        {
                            if (++skippedVCs_CS == 2)
                            {
                                clauseStart = clauseInfoPartID;
                            }
                        }
                    }
                }
            }
            //Return the retrieved limits. Either limit or both limits may still be -1.
            return new int[] { clauseStart, clauseEnd };
        }

        /* Takes the relevant sentences and formats their elements in order 
         * to identify cleft constructions and prompts. Returns a string list
         * of the partial IDs of those NCs which are focused by these
         * constructions.
         */
        private List<string> FindFocusedNPs(IEnumerable<XElement> sentences)
        {
            List<string> focusedNPs = new List<string>();

            //Format the elements appropriately so clefts and prompts
            //can be identified. Important elements are "what", "it", 
            //and the verb "be", as well as the partial IDs within the
            //formatted strings for NCs.
            foreach (XElement sentence in sentences)
            {
                List<string> sentElemList = new List<string>();

                foreach (XNode n in sentence.Nodes())
                {
                    if (n is XElement)
                    {
                        XElement nAsXElem = (XElement)n;
                        XElement lastTokenOfNode =
                            nAsXElem.Descendants("token").LastOrDefault();

                        string nID = nAsXElem.SplitID().Last();

                        bool nodeIsToken = (lastTokenOfNode == null);

                        if (nodeIsToken)
                        {
                            sentElemList.Add(nAsXElem.Attribute("pos").Value);
                        }
                        else
                        {
                            string POSListString = nAsXElem.Name.ToString();
                            bool foundCleftStarter = false;

                            if (nAsXElem.Name == "NC")
                            {
                                if (nAsXElem.Descendants().Count() == 1)
                                {
                                    string singleTokenLemma =
                                        nAsXElem.Descendants().First().Attribute("lemma").Value;
                                    if (singleTokenLemma.ToLower() == "it")
                                    {
                                        POSListString = "it";
                                        foundCleftStarter = true;
                                    }
                                    else if (singleTokenLemma.ToLower() == "what")
                                    {
                                        POSListString = "what";
                                        foundCleftStarter = true;
                                    }
                                }
                                if (!foundCleftStarter)
                                {
                                    POSListString += ":" + nID;
                                }
                            }
                            else if (nAsXElem.Name == "VC")
                            {
                                //Find the last token of the verb, if possible.
                                IEnumerable<XElement> tokensOfVC =
                                    from token in nAsXElem.Descendants("token")
                                    where token.Attribute("pos").Value.StartsWith("V")
                                    select token;
                                XElement lastTokenOfVC =
                                    tokensOfVC.LastOrDefault();

                                //Get the lemma of that token.
                                if (lastTokenOfVC != null
                                    && lastTokenOfVC.Attribute("lemma").Value == "be")
                                {
                                    POSListString = "be";
                                }
                            }
                            else if (nAsXElem.Name == "PC")
                            {
                                XElement embeddedNC = nAsXElem.Descendants("NC").FirstOrDefault();
                                if (embeddedNC != null)
                                {
                                    POSListString = "PC:NC:" + embeddedNC.SplitID().Last();
                                }
                            }
                            sentElemList.Add(POSListString);
                        }
                    }
                }
                //Join the formatted elements with "_".
                string stringToMatch = String.Join("_", sentElemList);

                //Use regular expressions to identify clefts and prompts.
                //Store partial IDs of focused NPs via groups -> (...). 
                //Index of groups starts with 1.
                Match ItCleftmMatch = Regex.Match(stringToMatch, @"^it_be_(PC:)?NC:([0-9]+)", RegexOptions.IgnoreCase);
                Match PromptMatch1 = Regex.Match(stringToMatch, @"^([A-Z]*)_?(PC:)?NC:([0-9]+)_,", RegexOptions.IgnoreCase);
                Match PromptMatch2 = Regex.Match(stringToMatch, @"^VC_(PRT_)?(PC:)?NC:([0-9]+)(_(PC:)?NC:([0-9]+))?_,", RegexOptions.IgnoreCase);

                //If the matches are successful, add the stored partial IDs to the list.
                if (ItCleftmMatch.Success)
                {
                    focusedNPs.Add(ItCleftmMatch.Groups[2].Value);
                }
                if (PromptMatch1.Success)
                {
                    focusedNPs.Add(PromptMatch1.Groups[3].Value);
                }
                if (PromptMatch2.Success)
                {
                    focusedNPs.Add(PromptMatch2.Groups[3].Value);
                }
            }

            return focusedNPs.Distinct().ToList();
        }

        /* Returns a string representation of the current pronoun.
         * Overrides the ToString() method of the class Object.
         */
        public override string ToString()
        {
            string output = "";
            output =
                output + "Pronoun:\t" + String.Join(":", ID)
                + " | " + Number + ", " + Gender
                + " | Ignore:" + IgnoreDefiniteness + ", " + String.Join(":", CollocData) + ", IR:" + NounIDforIR + ", CL:" + PrecClauseLimits[0] + ":" + PrecClauseLimits[1]
                + "\nFocus:\t\t" + String.Join("~", FocusedNPs)
                + "\nVCSel:\t\t" + String.Join("~", VCSelectedNPs)
                + "\nNCSel:\t\t" + String.Join("~", NCSelectedNPs);
            return output;
        }
    }
}
